using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Diagnostics;
using System.Xml;

namespace MyClever.Lib
{
    public class MissionPluginProxyNode
    {
        MissionPluginProxyNode myParent;
        Dictionary<string, object> loopDefinition;
        ArrayList pluginCollection;
        
        int currentPluginIndex;
        DataTableReader loopDataReader;
        
        public MissionPluginProxyNode Parent
        {
            get { return this.myParent; }
            set { this.myParent = value; }
        }

        public ArrayList Elements
        {
            get { return this.pluginCollection; }
        }

        public Dictionary<string, object> LoopDefinition
        {
            get { return this.loopDefinition; }
        }

        public MissionPluginProxyNode(Dictionary<string, object> definition)
        {
            this.myParent = null;
            this.loopDefinition = definition;
            this.pluginCollection = new ArrayList();

            this.currentPluginIndex = 0;
            this.loopDataReader = null;
        }

        public void AddPlugin(MissionPluginProxy plugin)
        {
            this.pluginCollection.Add(plugin);
        }

        public void AddPlugin(MissionPluginProxy plugin, bool addPrevious, int siblingPluginID)
        {
            int newPluginIndex = this.pluginCollection.IndexOf(this.GetPluginByID(siblingPluginID, false));

            if (false == addPrevious)
            {
                newPluginIndex++;
            }
            this.pluginCollection.Insert(newPluginIndex, plugin);
        }

        public void AddPluginNode(MissionPluginProxyNode pluginNode)
        {
            this.pluginCollection.Add(pluginNode);
            pluginNode.Parent = this;
        }

        public MissionPluginProxyNode GetRoot()
        {
            // start with this as root
            MissionPluginProxyNode root = this;
            // get the parent
            MissionPluginProxyNode parent = this.Parent;

            // keep going until no more parents
            while (null != parent)
            {
                // save the parent
                root = parent;
                // get the parent of the parent
                parent = parent.Parent;
            }
            return root;
        }

        /// <summary>
        /// Gets the plugin by an unique ID.
        /// </summary>
        /// <param name="pluginID">An unique plugin ID.</param>
        public MissionPluginProxy GetPluginByID(int pluginID, bool recursive)
        {
            foreach (MissionPluginProxy pluginOn in this.GetPlugins(recursive))
            {
                if (pluginID == pluginOn.ID)
                {
                    return pluginOn;
                }                
            }
            return null;
        }


        public MissionPluginProxyNode GetLoopByID(int loopID)
        {
            for (int i = 0; i < this.pluginCollection.Count; i++)
            {
                if (typeof(MissionPluginProxyNode) == this.pluginCollection[i].GetType())
                {
                    MissionPluginProxyNode pluginNodeOn = (MissionPluginProxyNode)this.pluginCollection[i];
                    if (loopID == Convert.ToInt32(pluginNodeOn.LoopDefinition["ID"]))
                    {
                        return pluginNodeOn;
                    }
                    else
                    {
                        if (null != pluginNodeOn.GetLoopByID(loopID))
                        {
                            return pluginNodeOn;
                        }
                    }
                }
            }
            return null;
        }


        //<summary>
        //Returns an unsorted list of all given plugins in this collection.
        //</summary>
        // <param name="recursive">if set to <c>true</c>, you get all plugins starting from the root node.</param>
        //<returns>A list of valid plugin instances.</returns>
        public List<MissionPluginProxy> GetPlugins(bool recursive)
        {
            List<MissionPluginProxy> plugins = new List<MissionPluginProxy>();
            foreach (object objectOn in this.pluginCollection)
            {
                if (typeof(MissionPluginProxy) == objectOn.GetType())
                {
                    MissionPluginProxy plugin = (MissionPluginProxy)objectOn;
                    plugins.Add(plugin);
                }
                else
                {
                    if (true == recursive)
                    {
                        MissionPluginProxyNode nextCollection = (MissionPluginProxyNode)objectOn;
                        plugins.AddRange(nextCollection.GetPlugins(recursive));   
                    }
                }
            }
            return plugins;
        }

        /// <summary>
        /// Returns a list of plugin ids, which have execute at least one error message.
        /// </summary>
        /// <returns></returns>
        public List<MissionPluginProxy> GetDefectivePlugins(bool recursive)
        {
            // todo: sind alle logging-Packeges mit 100% abgeschlossen?

            List<MissionPluginProxy> defectivePluginIDs = new List<MissionPluginProxy>();
            foreach (MissionPluginProxy pluginOn in this.GetPlugins(recursive))
            {
                if (true == pluginOn.HasErrorMessages())
                {
                    defectivePluginIDs.Add(pluginOn);
                }
            }
            return defectivePluginIDs;
        }

        public MissionPluginProxy MoveNext()
        {
            // while the next element is a loop-definition itself
            while (typeof(MissionPluginProxyNode) == this.pluginCollection[this.currentPluginIndex % this.pluginCollection.Count].GetType())
            {
                // start the new internal loop? (start recursion!)
                MissionPluginProxyNode nextLoop = (MissionPluginProxyNode)this.pluginCollection[this.currentPluginIndex % this.pluginCollection.Count];
                MissionPluginProxy nextPlugin = nextLoop.MoveNext();
                if (null != nextPlugin)
                {
                    // until we can return the next valid plugin to start
                    return nextPlugin;
                }
                else
                {
                    this.currentPluginIndex++;
                }            
            }
           
            // if we we START looping ...
            if (this.currentPluginIndex % this.pluginCollection.Count == 0)
            {
                // is this a real loop?
                // ATTENTION: the very first MissionPluginProxyNode (id = 0) is a virtual loop! it has no loop-definition
                if (null != this.loopDefinition && this.loopDefinition.Count > 0)
                {
                    // we must initialize the loopDataReader, if necessary
                    if (null == this.loopDataReader)
                    {
                        //the loopDataReader holds the data, the loop runs through
                        MissionPluginPackage linkedPackage;
                        MissionPluginPackageColumn linkedPackageColumn;
                        this.GetReferenceInstance(this.loopDefinition, out linkedPackage, out linkedPackageColumn);

                        MissionPluginPackage loopPackage = (MissionPluginPackage)linkedPackage.Clone();
                        loopPackage.CopyDataFromPackage(linkedPackage, this.loopDefinition);                        
                        this.loopDataReader = loopPackage.CreateDataReader();
                    }

                    // but if the loopDataReader is created (before)
                    if (null != this.loopDataReader)
                    {
                        // we must read a new row from the loop data
                        bool wasReadingSuccessful = this.loopDataReader.Read();
                        
                        //until the whole data were read
                        if (false == wasReadingSuccessful)
                        {
                            this.loopDataReader = null;
                            return null;
                        }
                    }

                }
                else
                {
                    if (this.currentPluginIndex >= this.pluginCollection.Count)
                    {
                        return null;
                    }
                }
            }

            MissionPluginProxy plugin = (MissionPluginProxy)this.pluginCollection[this.currentPluginIndex % this.pluginCollection.Count];
            plugin.Reset();

            // refresh linked packages
            foreach (MissionPluginPackage packageOn in plugin.GetPackages(Direction.Input))
            {
                if (true == packageOn.HasReference)
                {
                    MissionPluginPackage linkedPackage;
                    MissionPluginPackageColumn linkedColumn;
                    this.GetReferenceInstance(packageOn.Reference, out linkedPackage, out linkedColumn);

                    packageOn.CopyDataFromPackage(linkedPackage, packageOn.Reference);
                }
            }

            // refresh linked package-columns
            foreach (MissionPluginPackage packageOn in plugin.GetPackages(Direction.Input))
            {
                // we must get every row of this column                    
                List<MissionPluginPackageColumn> referencedColumns = packageOn.GetColumnsWithColumnReference();
                if (referencedColumns.Count > 0)
                {
                    int currentRow = 0;
                    do
                    {
                        DataRow newRow = packageOn.NewRow();

                        foreach (MissionPluginPackageColumn columnOn in referencedColumns)
                        {
                            MissionPluginPackage linkedPackage;
                            MissionPluginPackageColumn linkedColumn;
                            this.GetReferenceInstance(columnOn.ColumnReference, out linkedPackage, out linkedColumn);

                            if (currentRow >= 0 && currentRow < linkedColumn.Table.Rows.Count)
                            {
                                newRow[columnOn.ColumnName] = linkedColumn.Table.Rows[currentRow][linkedColumn.ColumnName];
                            }
                            else
                            {
                                currentRow = -1;
                            }
                        }
                        currentRow++;
                        if (currentRow > 0)
                        {
                            packageOn.Rows.Add(newRow);
                        }
                    } while (currentRow > 0);
                }
            }

            // refresh all cell references
            foreach (MissionPluginPackage packageOn in plugin.GetPackages(Direction.Input))
            {
                // write all given data-rows
                // use a for-loop here, to save the current rowIndex
                for (int rowIndex = 0; rowIndex < packageOn.Rows.Count; rowIndex++)
                {
                    DataRow rowOn = packageOn.Rows[rowIndex];

                    foreach (MissionPluginPackageColumn columnOn in packageOn.GetColumnsWithCellReference())
                    {
                        // if the current cell has a NULLDB-value
                        if (true == rowOn.IsNull(columnOn))
                        {
                            // get all references of the current cell, if available
                            string template;
                            Queue<Dictionary<string, object>> cellReferences = columnOn.GetCellReferences(rowIndex, out template);

                            // we replace every placeholder in the templates with the correct reference definition
                            string newCellValue = "";
                            string[] templateChilds = template.Split(new string[] { "{#}" }, StringSplitOptions.None);
                            foreach (string templateChildOn in templateChilds)
                            {
                                newCellValue += templateChildOn;

                                if (cellReferences.Count > 0)
                                {                  
                                    Dictionary<string, object> cellReferenceOn = cellReferences.Dequeue();
                                    newCellValue += this.GetRoot().GetLoopByID(Convert.ToInt32(cellReferenceOn["FromLoopID"])).loopDataReader[(string)cellReferenceOn["FromColumn"].ToString()];
                                }
                            }
                            rowOn[columnOn] = newCellValue;
                        }
                    }
                }
            }

            this.currentPluginIndex++;
            return plugin;
        }

        public void GetReferenceInstance(Dictionary<string, object> referenceData, out MissionPluginPackage packageInstance, out MissionPluginPackageColumn columnInstance)
        {
            // can not generate reference by loop link?
            Debug.Assert(false == referenceData.ContainsKey("FromLoopID"));
            
            int pluginID = Convert.ToInt32(referenceData["FromPluginID"]);
            string packageName = referenceData["FromPackage"].ToString();

            string columnName = "";
            if (true == referenceData.ContainsKey("FromColumn"))
            {
                columnName = referenceData["FromColumn"].ToString();
            }

            packageInstance = null;
            columnInstance = null;

            MissionPluginProxy linkedPlugin = this.GetRoot().GetPluginByID(pluginID, true);
            packageInstance = linkedPlugin.GetPackageByName(packageName);
            if (false == String.IsNullOrEmpty(columnName))
            {
                columnInstance = packageInstance.GetColumnByName(columnName);
            }
        }
    }
}
